package io.github.eric.urpc.client;

import io.github.eric.urpc.core.conn.Connection;
import org.apache.commons.pool2.PooledObject;
import org.apache.commons.pool2.PooledObjectFactory;
import org.apache.commons.pool2.impl.DefaultPooledObject;

/**
 *
 *    ┌──────────┐              borrow  ┌──────────────┐        ┌──────────────┐
 *    │makeObject│ ───►  Pool  ───────► │activateObject│ ─────► │validateObject│
 *    └──────────┘                      └──────────────┘        └──────────────┘
 *                        ▲
 *                        │                                             │
 *                                                                      ▼
 *              ┌───────────────┐       ┌──────────────┐
 *              │passivateObject│ ◄──── │validateObject│ ◄────────  doBusiness
 *              └───────────────┘       └──────────────┘   return
 *                                             │
 *                                             ▼
 *                                      ┌─────────────┐
 *                                      │destroyObject│
 *                                      └─────────────┘
 *
 */
public class ClientConnectionFactory implements PooledObjectFactory<Connection> {
    private final ClientContext clientContext;
    private final ClientConnector clientConnector;

    ClientConnectionFactory(ClientContext clientContext, ClientConnector clientConnector) {
        this.clientContext = clientContext;
        this.clientConnector = clientConnector;
    }

    /**
     * Creates an instance that can be served by the pool and wrap it in a PooledObject to be managed by the pool.
     *
     * makeObject() is called whenever a new instance is needed.
     * @return
     * @throws Exception
     */
    @Override
    public PooledObject<Connection> makeObject() throws Exception {
        return new DefaultPooledObject<>(clientConnector.connectOnce());
    }

    /**
     * Ensures that the instance is safe to be returned by the pool.
     *
     * activateObject(org.apache.commons.pool2.PooledObject<T>) is invoked on every instance that has been passivated before it is borrowed from the pool.
     * @param pooledObject
     * @throws Exception
     */
    @Override
    public void activateObject(PooledObject<Connection> pooledObject) throws Exception {
        clientContext.getConnectionManager().validateConnectionInitStatus(pooledObject.getObject());
    }

    /**
     * Destroys an instance no longer needed by the pool, using the default (NORMAL) DestroyMode.
     * It is important for implementations of this method to be aware that there is no guarantee about what state obj will be in and the implementation should be prepared to handle unexpected errors.
     *
     * Also, an implementation must take in to consideration that instances lost to the garbage collector may never be destroyed.
     *
     * destroyObject(org.apache.commons.pool2.PooledObject<T>) is invoked on every instance when it is being "dropped" from the pool
     * (whether due to the response from validateObject(org.apache.commons.pool2.PooledObject<T>), or for reasons specific to the pool implementation.)
     * There is no guarantee that the instance being destroyed will be considered active, passive or in a generally consistent state.
     * @param pooledObject
     * @throws Exception
     */
    @Override
    public void destroyObject(PooledObject<Connection> pooledObject) throws Exception {
        Connection connection = pooledObject.getObject();
        clientContext.getLogger().warn("[urpc client] destroy pooled object conn {}", connection.getId());
        connection.close();
    }

    /**
     * Uninitializes an instance to be returned to the idle object pool.
     *
     * passivateObject(org.apache.commons.pool2.PooledObject<T>) is invoked on every instance when it is returned to the pool.
     * @param pooledObject
     * @throws Exception
     */
    @Override
    public void passivateObject(PooledObject<Connection> pooledObject) throws Exception {

    }

    /**
     * validateObject(org.apache.commons.pool2.PooledObject<T>) may be invoked on activated instances to make sure they can be borrowed from the pool.
     * validateObject(org.apache.commons.pool2.PooledObject<T>) may also be used to test an instance being returned to the pool before it is passivated.
     * It will only be invoked on an activated instance.
     * @param pooledObject
     * @return
     */
    @Override
    public boolean validateObject(PooledObject<Connection> pooledObject) {
        Connection connection = pooledObject.getObject();
        boolean valid = connection.isConnected() && connection.isInitCompleted();
        if (!valid) {
            clientContext.getLogger().warn("[urpc client] pooled object conn {} is not connected or is not init completed", connection.getId());
        }
        return valid;
    }
}
